<!DOCTYPE html>
<html>
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  
  <title>你不知道的前端异常处理（万字长文，建议收藏） | lucifer的网络博客</title>
  
  <meta name="keywords" content="前端 leetocde 数据结构 算法 lucifer 大前端 性能优化 前端架构 前端工程化">
  
  
  <meta name="description" content="lucifer的个人博客，用来记录LeeCode刷题过程和心得，以及构建大前端知识体系">
  

  
  <link rel="alternate" href="/blog/atom.xml" title="lucifer的网络博客">
  

  <meta name="HandheldFriendly" content="True">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <!-- meta -->
  

  <!-- link -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css">
  
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/node-waves@0.7.6/dist/waves.min.css">
  
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@5.10.1/css/all.min.css">
  

  
  <link rel="shortcut icon" type="image/x-icon" href="https://avatars0.githubusercontent.com/u/12479470?s=400&u=442571e44cbd0b67e3503e9551d4445c78f593f8&v=4">
  

  
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-material-x@19.9.9/css/style.css">
  

  <script>
    function setLoadingBarProgress(num) {
      document.getElementById('loading-bar').style.width=num+"%";
    }
  </script>

  
    <!-- Global site tag (gtag.js) - Google Analytics -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-FVTTYT432Q"></script>
    <script>
      window.dataLayer = window.dataLayer || [];
      function gtag(){dataLayer.push(arguments);}
      gtag('js', new Date());
      gtag('config', 'G-FVTTYT432Q');
    </script>
  
  
    <!-- ba -->
    <script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?576ec211e11a69128667eb8c11a6cffe";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
    </script>
  
</head>

<body>
  
  
  <div class="cover-wrapper">
    <cover class='cover post half'>
      
        
  <h1 class='title'>lucifer</h1>


  <div class="m_search">
    <form name="searchform" class="form u-search-form">
      <input type="text" class="input u-search-input" placeholder="" />
      <i class="icon fas fa-search fa-fw"></i>
    </form>
  </div>

<div class='menu navgation'>
  <ul class='h-list'>
    
      
        <li>
          <a class="nav home" href="/blog/"
            
            
            id="blog">
            <i class='fas fa-home fa-fw'></i>&nbsp;主页
          </a>
        </li>
      
        <li>
          <a class="nav home" href="http://leetcode-solution.cn/"
            
            
            id="http:leetcode-solution.cn">
            <i class='fas fa-laptop-code fa-fw'></i>&nbsp;官网
          </a>
        </li>
      
        <li>
          <a class="nav home" href="/blog/friends/"
            
            
            id="blogfriends">
            <i class='fas fa-link fa-fw'></i>&nbsp;友链
          </a>
        </li>
      
        <li>
          <a class="nav home" href="/blog/about/"
            
            
            id="blogabout">
            <i class='fas fa-id-card-alt fa-fw'></i>&nbsp;联系我
          </a>
        </li>
      
    
  </ul>
</div>

      
    </cover>
    <header class="l_header pure">
  <div id="loading-bar-wrapper">
    <div id="loading-bar" class="pure"></div>
  </div>

	<div class='wrapper'>
		<div class="nav-main container container--flex">
      <a class="logo flat-box" href='/blog/' >
        
          lucifer的网络博客
        
      </a>
			<div class='menu navgation'>
				<ul class='h-list'>
          
  					
  						<li>
								<a class="nav flat-box" href="/blog/"
                  
                  
                  id="blog">
									<i class='fas fa-grin fa-fw'></i>&nbsp;回到主页
								</a>
							</li>
      			
  						<li>
								<a class="nav flat-box" href="/blog/categories/"
                  
                  
                  id="blogcategories">
									<i class='fas fa-folder-open fa-fw'></i>&nbsp;分类
								</a>
							</li>
      			
  						<li>
								<a class="nav flat-box" href="/blog/tags/"
                  
                  
                  id="blogtags">
									<i class='fas fa-hashtag fa-fw'></i>&nbsp;标签
								</a>
							</li>
      			
  						<li>
								<a class="nav flat-box" href="/blog/archives/"
                  
                  
                  id="blogarchives">
									<i class='fas fa-archive fa-fw'></i>&nbsp;归档
								</a>
							</li>
      			
      		
				</ul>
			</div>

			
				<div class="m_search">
					<form name="searchform" class="form u-search-form">
						<input type="text" class="input u-search-input" placeholder="搜索" />
						<i class="icon fas fa-search fa-fw"></i>
					</form>
				</div>
			
			<ul class='switcher h-list'>
				
					<li class='s-search'><a class="fas fa-search fa-fw" href='javascript:void(0)'></a></li>
				
				<li class='s-menu'><a class="fas fa-bars fa-fw" href='javascript:void(0)'></a></li>
			</ul>
		</div>

		<div class='nav-sub container container--flex'>
			<a class="logo flat-box"></a>
			<ul class='switcher h-list'>
				<li class='s-comment'><a class="flat-btn fas fa-comments fa-fw" href='javascript:void(0)'></a></li>
        
          <li class='s-toc'><a class="flat-btn fas fa-list fa-fw" href='javascript:void(0)'></a></li>
        
			</ul>
		</div>
	</div>
</header>
	<aside class="menu-phone">
    <header>
		<nav class="menu navgation">
      <ul>
        
          
            <li>
							<a class="nav flat-box" href="/blog/"
                
                
                id="blog">
								<i class='fas fa-clock fa-fw'></i>&nbsp;近期文章
							</a>
            </li>
          
            <li>
							<a class="nav flat-box" href="/blog/archives/"
                
                
                id="blogarchives">
								<i class='fas fa-archive fa-fw'></i>&nbsp;文章归档
							</a>
            </li>
          
            <li>
							<a class="nav flat-box" href="/blog/about/"
                
                
                id="blogabout">
								<i class='fas fa-info-circle fa-fw'></i>&nbsp;关于小站
							</a>
            </li>
          
       
      </ul>
		</nav>
    </header>
	</aside>
<script>setLoadingBarProgress(40);</script>

  </div>


  <div id="container" class="l_body">
    <div class='body-wrapper'>
      <div class='l_main'>
  

  <article id="post" class="post white-box article-type-post" itemscope itemprop="blogPost">
    


  <section class='meta'>
    
    
    <div class="meta" id="header-meta">
      
        
  
    <h1 class="title">
      <a href="/blog/2020/06/14/error-catch/">
        你不知道的前端异常处理（万字长文，建议收藏）
      </a>
    </h1>
  


      
      <div class='new-meta-box'>
        
          
        
          
            
  <div class='new-meta-item author'>
    
      <a href="https://lucifer.ren/blog" rel="nofollow">
        
          <img src="https://avatars0.githubusercontent.com/u/12479470?s=400&u=442571e44cbd0b67e3503e9551d4445c78f593f8&v=4">
        
        <p>lucifer</p>
      </a>
    
  </div>


          
        
          
            <div class="new-meta-item date">
  <a class='notlink'>
    <i class="fas fa-calendar-alt" aria-hidden="true"></i>
    <p>2020-06-14</p>
  </a>
</div>

          
        
          
            
  
  <div class='new-meta-item category'>
    <a href='/blog/categories/前端/' rel="nofollow">
      <i class="fas fa-folder-open" aria-hidden="true"></i>
      <p>前端</p>
    </a>
  </div>


          
        
          
            
  
    <div class="new-meta-item browse busuanzi">
      <a class='notlink'>
        <i class="fas fa-eye" aria-hidden="true"></i>
        <p>
          <span id="busuanzi_value_page_pv">
            <i class="fas fa-spinner fa-spin fa-fw" aria-hidden="true"></i>
          </span>
        </p>
      </a>
    </div>
  


          
        
          
            

          
        
          
            
  
    <div style="margin-right: 10px;">
      <span class="post-time">
        <span class="post-meta-item-icon">
          <i class="fa fa-keyboard"></i>
          <span class="post-meta-item-text">  字数统计: </span>
          <span class="post-count">8.2k字</span>
        </span>
      </span>
      &nbsp; | &nbsp;
      <span class="post-time">
        <span class="post-meta-item-icon">
          <i class="fa fa-hourglass-half"></i>
          <span class="post-meta-item-text">  阅读时长≈</span>
          <span class="post-count">31分</span>
        </span>
      </span>
    </div>
  

          
        
      </div>
      
        <hr>
      
    </div>
  </section>


    <section class="article typo">
      <div class="article-entry" itemprop="articleBody">
        <p>除了调试，处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道，就好像我们天天和 Bug 打交道一样。因此正确认识异常，并作出合适的异常处理就显得很重要了。</p>
<p>我们先尝试抛开前端这个限定条件，来看下更广泛意义上程序的报错以及异常处理。不管是什么语言，都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常，并针对其做相应的<strong>异常处理</strong>。</p>
<p>然而，很多人对异常的处理方式是<strong>事后修补</strong>，即某个异常发生的时候，增加对应的条件判断，这真的是一种非常低效的开发方式，非常不推荐大家这么做。那么究竟如何正确处理异常呢？由于不同语言有不同的特性，因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就<strong>前端</strong>异常进行详细阐述，但是读者也可以稍加修改延伸到其他各个领域。</p>
<blockquote>
<p>本文讨论的异常指的是软件异常，而非硬件异常。</p>
</blockquote>
<a id="more"></a>

<h2 id="什么是异常"><a href="#什么是异常" class="headerlink" title="什么是异常"></a>什么是异常</h2><p>用直白的话来解释异常的话，就是<strong>程序发生了意想不到的情况，这种情况影响到了程序的正确运行</strong>。</p>
<p>从根本上来说，异常就是一个<strong>数据结构</strong>，其保存了异常发生的相关信息，比如错误码，错误信息等。以 JS 中的标准内置对象 Error 为例，其标准属性有 name 和 message。然而不同的浏览器厂商有自己的自定义属性，这些属性并不通用。比如 Mozilla 浏览器就增加了 filename 和 stack 等属性。</p>
<p>值得注意的是错误只有被抛出，才会产生异常，不被抛出的错误不会产生异常。比如：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"start"</span>);</span><br><span class="line">  <span class="keyword">new</span> <span class="built_in">Error</span>();</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"end"</span>);</span><br><span class="line">&#125;</span><br><span class="line">t();</span><br></pre></td></tr></table></figure>

<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfsy45k62og30760h20ug.gif" alt></p>
<p>（动画演示）</p>
<p>这段代码不会产生任何的异常，控制台也不会有任何错误输出。</p>
<h2 id="异常的分类"><a href="#异常的分类" class="headerlink" title="异常的分类"></a>异常的分类</h2><p>按照产生异常时程序是否正在运行，我们可以将错误分为<strong>编译时异常</strong>和<strong>运行时异常</strong>。</p>
<p>编译时异常指的是源代码在编译成可执行代码之前产生的异常。而运行时异常指的是可执行代码被装载到内存中执行之后产生的异常。</p>
<h3 id="编译时异常"><a href="#编译时异常" class="headerlink" title="编译时异常"></a>编译时异常</h3><p>我们知道 TS 最终会被编译成 JS，从而在 <code>JS Runtime</code>中执行。既然存在编译，就有可能编译失败，就会有编译时异常。</p>
<p>比如我使用 TS 写出了如下代码：</p>
<figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> s: <span class="built_in">string</span> = <span class="number">123</span>;</span><br></pre></td></tr></table></figure>

<p>这很明显是错误的代码， 我给 s 声明了 string 类型，但是却给它赋值 number。</p>
<p>当我使用 tsc（typescript 编译工具，全称是 typescript compiler）尝试编译这个文件的时候会有异常抛出：</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">tsc a.ts</span><br><span class="line">a.ts:1:7 - error TS2322: Type <span class="string">'123'</span> is not assignable to <span class="built_in">type</span> <span class="string">'string'</span>.</span><br><span class="line"></span><br><span class="line">1 const s: string = 123;</span><br><span class="line">        ~</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Found 1 error.</span><br></pre></td></tr></table></figure>

<p>这个异常就是编译时异常，因为我的代码还没有执行。</p>
<p>然而并不是你用了 TS 才存在编译时异常，JS 同样有编译时异常。有的人可能会问 JS 不是解释性语言么？是边解释边执行，没有编译环节，怎么会有编译时异常？</p>
<p>别急，我举个例子你就明白了。如下代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'start'</span>)</span><br><span class="line">  <span class="keyword">await</span> sa</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">'end'</span>)</span><br><span class="line">&#125;</span><br><span class="line">t()</span><br></pre></td></tr></table></figure>

<p>上面的代码由于存在语法错误，不会编译通过，因此并不会打印<code>start</code>，侧面证明了这是一个编译时异常。尽管 JS 是解释语言，也依然存在编译阶段，这是必然的，因此自然也会有编译异常。</p>
<p>总的来说，编译异常可以在代码被编译成最终代码前被发现，因此对我们的伤害更小。接下来，看一下令人心生畏惧的<strong>运行时异常</strong>。</p>
<h3 id="运行时异常"><a href="#运行时异常" class="headerlink" title="运行时异常"></a>运行时异常</h3><p>相信大家对运行时异常非常熟悉。这恐怕是广大前端碰到最多的异常类型了。众所周知的 <a href="https://zh.wikipedia.org/wiki/%E7%A9%BA%E6%8C%87%E6%A8%99#NullPointerException" title="Null Pointer Exception" target="_blank" rel="noopener">NPE（Null Pointer Exception）</a> 就是运行时异常。</p>
<p>将上面的例子稍加改造，得到下面代码：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"start"</span>);</span><br><span class="line">  <span class="keyword">throw</span> <span class="number">1</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"end"</span>);</span><br><span class="line">&#125;</span><br><span class="line">t();</span><br></pre></td></tr></table></figure>

<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfsy7388tng30760h2gn6.gif" alt></p>
<p>（动画演示）</p>
<blockquote>
<p>注意 end 没有打印，并且 t 没有弹出栈。实际上 t 最终还是会被弹出的，只不过和普通的返回不一样。</p>
</blockquote>
<p>如上，则会打印出<code>start</code>。由于异常是在代码运行过程中抛出的，因此这个异常属于运行时异常。相对于编译时异常，这种异常更加难以发现。上面的例子可能比较简单，但是如果我的异常是隐藏在某一个流程控制语句（比如 if else）里面呢？程序就可能在客户的电脑走入那个抛出异常的 if 语句，而在你的电脑走入另一条。这就是著名的 <strong>《在我电脑上好好的》</strong> 事件。</p>
<h2 id="异常的传播"><a href="#异常的传播" class="headerlink" title="异常的传播"></a>异常的传播</h2><p>异常的传播和我之前写的<a href="https://lucifer.ren/blog/2019/12/11/browser-event/" title="浏览器事件模型">浏览器事件模型</a>有很大的相似性。只不过那个是作用在 <strong>DOM 这样的数据结构</strong>，这个则是作用在<strong>函数调用栈这种数据结构</strong>，并且事件传播存在捕获阶段，异常传播是没有的。不同 C 语言，JS 中异常传播是自动的，不需要程序员手动地一层层传递。如果一个异常没有被 catch，它会沿着函数调用栈一层层传播直到栈空。</p>
<p>异常处理中有两个关键词，它们是<strong>throw（抛出异常）</strong> 和 <strong>catch（处理异常）</strong>。 当一个异常被抛出的时候，异常的传播就开始了。异常会不断传播直到遇到第一个 catch。 如果程序员没有手动 catch，那么一般而言程序会抛出类似<strong>unCaughtError</strong>，表示发生了一个异常，并且这个异常没有被程序中的任何 catch 语言处理。未被捕获的异常通常会被打印在控制台上，里面有详细的堆栈信息，从而帮助程序员快速排查问题。实际上我们的程序的目标是<strong>避免 unCaughtError</strong>这种异常，而不是一般性的异常。</p>
<h3 id="一点小前提"><a href="#一点小前提" class="headerlink" title="一点小前提"></a>一点小前提</h3><p>由于 JS 的 Error 对象没有 code 属性，只能根据 message 来呈现，不是很方便。我这里进行了简单的扩展，后面很多地方我用的都是自己扩展的 Error ，而不是原生 JS Error ，不再赘述。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">oldError = <span class="built_in">Error</span>;</span><br><span class="line"><span class="built_in">Error</span> = <span class="function"><span class="keyword">function</span> (<span class="params">&#123; code, message, fileName, lineNumber &#125;</span>) </span>&#123;</span><br><span class="line">  error = <span class="keyword">new</span> oldError(message, fileName, lineNumber);</span><br><span class="line">  error.code = code;</span><br><span class="line">  <span class="keyword">return</span> error;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<h3 id="手动抛出-or-自动抛出"><a href="#手动抛出-or-自动抛出" class="headerlink" title="手动抛出 or 自动抛出"></a>手动抛出 or 自动抛出</h3><p>异常既可以由程序员自己手动抛出，也可以由程序自动抛出。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">`I'm Exception`</span>);</span><br></pre></td></tr></table></figure>

<p>（手动抛出的例子）</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">a = <span class="literal">null</span>;</span><br><span class="line">a.toString(); <span class="comment">// Thrown: TypeError: Cannot read property 'toString' of null</span></span><br></pre></td></tr></table></figure>

<p>（程序自动抛出的例子）</p>
<p>自动抛出异常很好理解，毕竟我们哪个程序员没有看到过程序自动抛出的异常呢？</p>
<blockquote>
<p>“这个异常突然就跳出来！吓我一跳！”，某不知名程序员如是说。</p>
</blockquote>
<p>那什么时候应该手动抛出异常呢？</p>
<p>一个指导原则就是<strong>你已经预知到程序不能正确进行下去了</strong>。比如我们要实现除法，首先我们要考虑的是被除数为 0 的情况。当被除数为 0 的时候，我们应该怎么办呢？是抛出异常，还是 return 一个特殊值？答案是都可以，你自己能区分就行，这没有一个严格的参考标准。 我们先来看下抛出异常，告诉调用者<strong>你的输入，我处理不了</strong>这种情况。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">divide</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">  a = +a;</span><br><span class="line">  b = +b; <span class="comment">// 转化成数字</span></span><br><span class="line">  <span class="keyword">if</span> (!b) &#123;</span><br><span class="line">    <span class="comment">// 匹配 +0, -0, NaN</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(&#123;</span><br><span class="line">      code: <span class="number">1</span>,</span><br><span class="line">      message: <span class="string">"Invalid dividend "</span> + b,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">Number</span>.isNaN(a)) &#123;</span><br><span class="line">    <span class="comment">// 匹配 NaN</span></span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(&#123;</span><br><span class="line">      code: <span class="number">2</span>,</span><br><span class="line">      message: <span class="string">"Invalid divisor "</span> + a,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> a / b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面代码会在两种情况下抛出异常，告诉调用者你的输入我处理不了。由于这两个异常都是程序员自动手动抛出的，因此是<strong>可预知的异常</strong>。</p>
<p>刚才说了，我们也可以通过返回值来区分<code>异常输入</code>。我们来看下返回值输入是什么，以及和异常有什么关系。</p>
<h3 id="异常-or-返回"><a href="#异常-or-返回" class="headerlink" title="异常 or 返回"></a>异常 or 返回</h3><p>如果是基于异常形式（遇到不能处理的输入就抛出异常）。当别的代码调用<code>divide</code>的时候，需要自己 catch。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    divide(<span class="string">"foo"</span>, <span class="string">"bar"</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"被除数必须是除0之外的数"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="number">2</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"除数必须是数字"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"不可预知的错误"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>然而就像上面我说的那样，divide 函数设计的时候，也完全可以不用异常，而是使用返回值来区分。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">divide</span>(<span class="params">a, b</span>) </span>&#123;</span><br><span class="line">  a = +a;</span><br><span class="line">  b = +b; <span class="comment">// 转化成数字</span></span><br><span class="line">  <span class="keyword">if</span> (!b) &#123;</span><br><span class="line">    <span class="comment">// 匹配 +0, -0, NaN</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Error</span>(&#123;</span><br><span class="line">      code: <span class="number">1</span>,</span><br><span class="line">      message: <span class="string">"Invalid dividend "</span> + b,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (<span class="built_in">Number</span>.isNaN(a)) &#123;</span><br><span class="line">    <span class="comment">// 匹配 NaN</span></span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Error</span>(&#123;</span><br><span class="line">      code: <span class="number">2</span>,</span><br><span class="line">      message: <span class="string">"Invalid divisor "</span> + a,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> a / b;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当然，我们使用方式也要作出相应改变。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">const</span> res = divide(<span class="string">"foo"</span>, <span class="string">"bar"</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (res.code === <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"被除数必须是除0之外的数"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (res.code === <span class="number">2</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"除数必须是数字"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"不可预知的错误"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这种函数设计方式和抛出异常的设计方式从功能上说都是一样的，只是告诉调用方的方式不同。如果你选择第二种方式，而不是抛出异常，那么实际上需要调用方书写额外的代码，用来区分正常情况和异常情况，这并不是一种良好的编程习惯。</p>
<p>然而在 Go 等返回值可以为复数的语言中，我们无需使用上面蹩脚的方式，而是可以：</p>
<figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">res, err := divide(<span class="string">"foo"</span>, <span class="string">"bar"</span>);</span><br><span class="line"><span class="keyword">if</span> err != <span class="literal">nil</span> &#123;</span><br><span class="line">    log.Fatal(err)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>这是和 Java 和 JS 等语言使用的 try catch 不一样的的地方，Go 是通过 panic recover defer 机制来进行异常处理的。感兴趣的可以去看看 <a href="https://github.com/golang/go/blob/master/src/os/error_test.go" title="Go 源码关于错误测试部分" target="_blank" rel="noopener">Go 源码关于错误测试部分</a></p>
<p>可能大家对 Go 不太熟悉。没关系，我们来继续看下 shell。实际上 shell 也是通过返回值来处理异常的，我们可以通过 $? 拿到上一个命令的返回值，这本质上也是一种调用栈的传播行为，而且是通过返回值而不是捕获来处理异常的。</p>
<blockquote>
<p>作为函数返回值处理和 try catch 一样，这是语言的设计者和开发者共同决定的一件事情。</p>
</blockquote>
<p>上面提到了异常传播是作用在<strong>函数调用栈</strong>上的。当一个异常发生的时候，其会沿着函数调用栈逐层返回，直到第一个 catch 语句。当然 catch 语句内部仍然可以触发异常（自动或者手动）。如果 catch 语句内部发生了异常，也一样会沿着其函数调用栈继续执行上述逻辑，专业术语是 <strong>stack unwinding</strong>。</p>
<blockquote>
<p>实际上并不是所有的语言都会进行 stack unwinding，这个我们会在接下来的《运行时异常可以恢复么？》部分讲解。</p>
</blockquote>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfsax74xjdj30ya0s040p.jpg" alt></p>
<p>伪代码来描述一下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">bubble</span>(<span class="params">error, fn</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">if</span> (fn.hasCatchBlock()) &#123;</span><br><span class="line">    runCatchCode(error);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (callstack.isNotEmpty()) &#123;</span><br><span class="line">    bubble(error, callstack.pop());</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<blockquote>
<p>从我的伪代码可以看出所谓的 stack unwinding 其实就是 callstack.pop()</p>
</blockquote>
<p>这就是异常传播的一切！仅此而已。</p>
<h2 id="异常的处理"><a href="#异常的处理" class="headerlink" title="异常的处理"></a>异常的处理</h2><p>我们已经了解来异常的传播方式了。那么接下来的问题是，我们应该如何在这个传播过程中处理异常呢？</p>
<p>我们来看一个简单的例子：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  b();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  c();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"an error  occured"</span>);</span><br><span class="line">&#125;</span><br><span class="line">a();</span><br></pre></td></tr></table></figure>

<p>我们将上面的代码放到 chrome 中执行， 会在控制台显示如下输出：</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfs9viibxvj30gq08qjsf.jpg" alt></p>
<p>我们可以清楚地看出函数的调用关系。即错误是在 c 中发生的，而 c 是 b 调用的，b 是 a 调用的。这个函数调用栈是为了方便开发者定位问题而存在的。</p>
<p>上面的代码，我们并没有 catch 错误，因此上面才会有<strong>uncaught Error</strong>。</p>
<p>那么如果我们 catch ，会发生什么样的变化呢？catch 的位置会对结果产生什么样的影响？在 a ，b，c 中 catch 的效果是一样的么？</p>
<p>我们来分别看下：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  b();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  c();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"an error  occured"</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">a();</span><br></pre></td></tr></table></figure>

<p>(在 c 中 catch)</p>
<p>我们将上面的代码放到 chrome 中执行， 会在控制台显示如下输出：</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfsa1idhyij30fw04at96.jpg" alt></p>
<p>可以看出，此时已经没有<strong>uncaught Error</strong>啦，仅仅在控制台显示了<strong>标准输出</strong>，而<strong>非错误输出</strong>（因为我用的是 console.log，而不是 console.error）。然而更重要是的是，如果我们没有 catch，那么后面的同步代码将不会执行。</p>
<p>比如在 c 的 throw 下面增加一行代码，这行代码是无法被执行的，<strong>无论这个错误有没有被捕获</strong>。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"an error  occured"</span>);</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"will never run"</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们将 catch 移动到 b 中试试看。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  b();</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    c();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"an error  occured"</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">a();</span><br></pre></td></tr></table></figure>

<p>(在 b 中 catch)</p>
<p>在这个例子中，和上面在 c 中捕获没有什么本质不同。其实放到 a 中捕获也是一样，这里不再贴代码了，感兴趣的自己试下。</p>
<p>既然处于函数调用栈顶部的函数报错， 其函数调用栈下方的任意函数都可以进行捕获，并且效果没有本质不同。那么问题来了，我到底应该在哪里进行错误处理呢？</p>
<p>答案是责任链模式。我们先来简单介绍一下责任链模式，不过细节不会在这里展开。</p>
<p>假如 lucifer 要请假。</p>
<ul>
<li>如果请假天数小于等于 1 天，则主管同意即可</li>
<li>如果请假大于 1 天，但是小于等于三天，则需要 CTO 同意。</li>
<li>如果请假天数大于三天，则需要老板同意。</li>
</ul>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfsbg56kx0j31tk0bc40k.jpg" alt></p>
<p>这就是一个典型的责任链模式。谁有责任干什么事情是确定的，不要做自己能力范围之外的事情。比如主管不要去同意大于 1 天的审批。</p>
<p>举个例子，假设我们的应用有三个异常处理类，它们分别是：<code>用户输入错误</code>，<code>网络错误</code> 和 <code>类型错误</code>。如下代码，当代码执行的时候会报错一个<code>用户输入异常</code>。这个异常没有被 C 捕获，会 unwind stack 到 b，而 b 中 catch 到这个错误之后，通过查看 code 值判断其可以被处理，于是打印<code>I can handle this</code>。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    b();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="string">"NETWORK_ERROR"</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"I can handle this"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// can't handle, pass it down</span></span><br><span class="line">    <span class="keyword">throw</span> err;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    c();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="string">"INPUT_ERROR"</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"I can handle this"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// can't handle, pass it down</span></span><br><span class="line">    <span class="keyword">throw</span> err;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(&#123;</span><br><span class="line">    code: <span class="string">"INPUT_ERROR"</span>,</span><br><span class="line">    message: <span class="string">"an error  occured"</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">a();</span><br></pre></td></tr></table></figure>

<p>而如果 c 中抛出的是别的异常，比如<code>网络异常</code>，那么 b 是无法处理的，虽然 b catch 住了，但是由于你无法处理，因此一个好的做法是<code>继续抛出异常</code>，而不是<strong>吞没</strong>异常。不要畏惧错误，抛出它。<strong>只有没有被捕获的异常才是可怕的</strong>，如果一个错误可以被捕获并得到正确处理，它就不可怕。</p>
<p>举个例子：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">a</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    b();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="string">"NETWORK_ERROR"</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"I can handle this"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// can't handle, pass it down</span></span><br><span class="line">    <span class="keyword">throw</span> err;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">b</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    c();</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="string">"INPUT_ERROR"</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"I can handle this"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">c</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(&#123;</span><br><span class="line">    code: <span class="string">"NETWORK_ERROR"</span>,</span><br><span class="line">    message: <span class="string">"an error  occured"</span>,</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">a();</span><br></pre></td></tr></table></figure>

<p>如上代码不会有任何异常被抛出，它被完全吞没了，这对我们调试问题简直是灾难。因此切记<strong>不要吞没你不能处理的异常</strong>。正确的做法应该是上面讲的那种<strong>只 catch 你可以处理的异常，而将你不能处理的异常 throw 出来</strong>，这就是责任链模式的典型应用。</p>
<p>这只是一个简单的例子，就足以绕半天。实际业务肯定比这个复杂多得多。因此异常处理绝对不是一件容易的事情。</p>
<p>如果说谁来处理是一件困难的事情，那么在异步中决定谁来处理异常就是难上加难，我们来看下。</p>
<h2 id="同步与异步"><a href="#同步与异步" class="headerlink" title="同步与异步"></a>同步与异步</h2><p>同步异步一直是前端难以跨越的坎，对于异常处理也是一样。以 NodeJS 中用的比较多的<strong>读取文件</strong> API 为例。它有两个版本，一个是异步，一个是同步。同步读取仅仅应该被用在没了这个文件无法进行下去的时候。比如读取一个配置文件。而不应该在比如浏览器中读取用户磁盘上的一个图片等，这样会造成主线程阻塞，导致浏览器卡死。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 异步读取文件</span></span><br><span class="line">fs.readFileSync();</span><br><span class="line"><span class="comment">// 同步读取文件</span></span><br><span class="line">fs.readFile();</span><br></pre></td></tr></table></figure>

<p>当我们试图<strong>同步</strong>读取一个不存在的文件的时候，会抛出以下异常：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fs.readFileSync(<span class="string">'something-not-exist.lucifer'</span>);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'脑洞前端'</span>);</span><br><span class="line">Thrown:</span><br><span class="line"><span class="built_in">Error</span>: ENOENT: no such file or directory, open <span class="string">'something-not-exist.lucifer'</span></span><br><span class="line">    at <span class="built_in">Object</span>.openSync (fs.js:<span class="number">446</span>:<span class="number">3</span>)</span><br><span class="line">    at <span class="built_in">Object</span>.readFileSync (fs.js:<span class="number">348</span>:<span class="number">35</span>) &#123;</span><br><span class="line">  errno: <span class="number">-2</span>,</span><br><span class="line">  syscall: <span class="string">'open'</span>,</span><br><span class="line">  code: <span class="string">'ENOENT'</span>,</span><br><span class="line">  path: <span class="string">'something-not-exist.lucifer'</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>并且<code>脑洞前端</code>是不会被打印出来的。这个比较好理解，我们上面已经解释过了。</p>
<p>而如果以异步方式的话：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">fs.readFile(<span class="string">'something-not-exist.lucifer'</span>, (err, data) =&gt; &#123;<span class="keyword">if</span>(err) &#123;<span class="keyword">throw</span> err&#125;&#125;);</span><br><span class="line"><span class="built_in">console</span>.log(<span class="string">'lucifer'</span>)</span><br><span class="line">lucifer</span><br><span class="line"><span class="literal">undefined</span></span><br><span class="line">Thrown:</span><br><span class="line">[<span class="built_in">Error</span>: ENOENT: no such file or directory, open <span class="string">'something-not-exist.lucifer'</span>] &#123;</span><br><span class="line">  errno: <span class="number">-2</span>,</span><br><span class="line">  code: <span class="string">'ENOENT'</span>,</span><br><span class="line">  syscall: <span class="string">'open'</span>,</span><br><span class="line">  path: <span class="string">'something-not-exist.lucifer'</span></span><br><span class="line">&#125;</span><br><span class="line">&gt;</span><br></pre></td></tr></table></figure>

<p><code>脑洞前端</code>是会被打印出来的。</p>
<p>其本质在于 fs.readFile 的函数调用已经成功，并从调用栈返回并执行到下一行的<code>console.log(&#39;lucifer&#39;)</code>。因此错误发生的时候，调用栈是空的，这一点可以从上面的错误堆栈信息中看出来。</p>
<blockquote>
<p>不明白为什么调用栈是空的同学可以看下我之前写的<a href="https://lucifer.ren/blog/2019/12/11/event-loop/" title="《一文看懂浏览器事件循环》">《一文看懂浏览器事件循环》</a></p>
</blockquote>
<p>而 try catch 的作用仅仅是捕获当前调用栈的错误（上面异常传播部分已经讲过了）。因此异步的错误是无法捕获的，比如；</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  fs.readFile(<span class="string">"something-not-exist.lucifer"</span>, (err, data) =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (err) &#123;</span><br><span class="line">      <span class="keyword">throw</span> err;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"catching an error"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的 <code>catching an error</code> 不会被打印。因为错误抛出的时候， 调用栈中不包含这个 catch 语句，而仅仅在执行<code>fs.readFile</code>的时候才会。</p>
<p>如果我们换成同步读取文件的例子看看：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  fs.readFileSync(<span class="string">"something-not-exist.lucifer"</span>);</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"catching an error"</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>上面的代码会打印 <code>catching an error</code>。因为读取文件被同步发起，文件返回之前线程会被挂起，当线程恢复执行的时候， fs.readFileSync 仍然在函数调用栈中，因此 fs.readFileSync 产生的异常会冒泡到 catch 语句。</p>
<p>简单来说就是<strong>异步产生的错误不能用 try catch 捕获，而要使用回调捕获。</strong></p>
<p>可能有人会问了，我见过用 try catch 捕获异步异常啊。 比如：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">rejectIn = <span class="function">(<span class="params">ms</span>) =&gt;</span></span><br><span class="line">  <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function">(<span class="params">_, r</span>) =&gt;</span> &#123;</span><br><span class="line">    setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">      r(<span class="number">1</span>);</span><br><span class="line">    &#125;, ms);</span><br><span class="line">  &#125;);</span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">await</span> rejectIn(<span class="number">0</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"catching an error"</span>, err);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">t();</span><br></pre></td></tr></table></figure>

<p>本质上这只是一个语法糖，是 Promise.prototype.catch 的一个语法糖而已。而这一语法糖能够成立的原因在于其用了 Promise 这种包装类型。如果你不用包装类型，比如上面的 fs.readFile 不用 Promise 等包装类型包装，打死都不能用 try catch 捕获。</p>
<p>而如果我们使用 babel 转义下，会发现 try catch 不见了，变成了 switch case 语句。这就是 try catch “可以捕获异步异常”的原因，仅此而已，没有更多。</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gfsyweyu9tj30l90fajtj.jpg" alt><br>（babel 转义结果）</p>
<p>我使用的 babel 转义环境都记录在<a href="https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=usage&spec=true&loose=true&code_lz=E4UwViDGAuCSB2ACAvIgFAWwM4EoUD4AoRReEAd0QAVgB7DASyxDTQH0AaRYPZfRAN7ESiZtAAqDDCFoBXaK178hIkcDQBGHAG5hJAL5dsO4fpMBDLAE94kRADNZt6A1pIFeFYmjArgvYjm5OYM0NzgUHDwaAAMJgaIkObQkAAW6CDAPP6qkG5YtAA2IAB0hbQA5mgAREkpqQzwFYFImXTA1Vxt8Yj6hH2EHtpAA&debug=false&forceAllTransforms=true&shippedProposals=true&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=false&presets=env%2Ces2015%2Ces2016%2Ces2017%2Creact%2Cstage-0%2Cstage-1%2Cstage-2%2Cstage-3%2Ces2015-loose%2Ctypescript%2Cflow%2Cenv&prettier=false&targets=Electron-1.8%252CNode-10.13&version=7.10.2&externalPlugins=%40babel%2Fplugin-transform-arrow-functions%407.8.3" title="babel 转义环境" target="_blank" rel="noopener">这里</a>，大家可以直接点开链接查看.</p>
<blockquote>
<p>虽然浏览器并不像 babel 转义这般实现，但是至少我们明白了一点。目前的 try catch 的作用机制是无法捕获异步异常的。</p>
</blockquote>
<p>异步的错误处理推荐使用容器包装，比如 Promise。然后使用 catch 进行处理。实际上 Promise 的 catch 和 try catch 的 catch 有很多相似的地方，大家可以类比过去。</p>
<p>和同步处理一样，很多原则都是通用的。比如异步也不要去吞没异常。下面的代码是不好的，因为它吞没了<strong>它不能处理的</strong>异常。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">p = <span class="built_in">Promise</span>.reject(<span class="number">1</span>);</span><br><span class="line">p.catch(<span class="function"><span class="params">()</span> =&gt;</span> &#123;&#125;);</span><br></pre></td></tr></table></figure>

<p>更合适的做法的应该是类似这种：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">p = <span class="built_in">Promise</span>.reject(<span class="number">1</span>);</span><br><span class="line">p.catch(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (err == <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"I can handle this"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">throw</span> err;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>

<h2 id="彻底消除运行时异常可能么？"><a href="#彻底消除运行时异常可能么？" class="headerlink" title="彻底消除运行时异常可能么？"></a>彻底消除运行时异常可能么？</h2><p>我个人对目前前端现状最为头疼的一点是：<strong>大家过分依赖运行时，而严重忽略编译时</strong>。我见过很多程序，你如果不运行，根本不知道程序是怎么走的，每个变量的 shape 是什么。怪不得处处都可以看到 console.log。我相信你一定对此感同身受。也许你就是那个写出这种代码的人，也许你是给别人擦屁股的人。为什么会这样？ 就是因为大家太依赖运行时。TS 的出现很大程度上改善了这一点，前提是你用的是 typescript，而不是 anyscript。其实 eslint 以及 stylint 对此也有贡献，毕竟它们都是静态分析工具。</p>
<p>我强烈建议将异常保留在编译时，而不是运行时。不妨极端一点来看：假如所有的异常都在编译时发生，而一定不会在运行时发生。那么我们是不是就可以<strong>信心满满</strong>地对应用进行重构啦?</p>
<p>幸运的是，我们能够做到。只不过如果当前语言做不到的话，则需要对现有的语言体系进行改造。这种改造成本真的很大。不仅仅是 API，编程模型也发生了翻天覆地的变化，不然函数式也不会这么多年没有得到普及了。</p>
<blockquote>
<p>不熟悉函数编程的可以看看我之前写的<a href="https://github.com/azl397985856/functional-programming" title="函数式编程入门篇" target="_blank" rel="noopener">函数式编程入门篇</a>。</p>
</blockquote>
<p>如果才能彻底消除异常呢？在回答这个问题之前，我们先来看下一门号称<strong>没有运行时异常</strong>的语言 <a href="https://guide.elm-lang.org/error_handling/" target="_blank" rel="noopener">elm</a>。elm 是一门可以编译为 JS 的函数式编程语言，其封装了诸如网络 IO 等副作用，是一种声明式可推导的语言。 有趣的是，elm 也有异常处理。 elm 中关于异常处理（Error Handling）部分有两个小节的内容，分别是：<code>Maybe</code> 和 <code>Result</code>。elm 之所以没有运行时异常的一个原因就是它们。 一句话概括“为什么 elm 没有异常”的话，那就是<strong>elm 把异常看作数据（data）</strong>。</p>
<p>举个简单的例子：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">maybeResolveOrNot = <span class="function">(<span class="params">ms</span>) =&gt;</span></span><br><span class="line">  setTimeout(<span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="built_in">Math</span>.random() &gt; <span class="number">0.5</span>) &#123;</span><br><span class="line">      <span class="built_in">console</span>.log(<span class="string">"ok"</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"error"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;);</span><br></pre></td></tr></table></figure>

<p>上面的代码有一半的可能报错。那么在 elm 中就不允许这样的情况发生。所有的可能发生异常的代码都会被强制包装一层容器，这个容器在这里是 Maybe。</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gft3bwkkufj30hh082aac.jpg" alt></p>
<p>在其他函数式编程语言名字可能有所不同，但是意义相同。实际上，不仅仅是异常，正常的数据也会被包装到容器中，你需要通过容器的接口来获取数据。如果难以理解的话，你可以将其简单理解为 Promsie（但并不完全等价）。</p>
<p>Maybe 可能返回正常的数据 data，也可能会生成一个错误 error。某一个时刻只能是其中一个，并且只有运行的时候，我们才真正知道它是什么。从这一点来看，有点像薛定谔的猫。</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gft3dcn99mj30yn0jkgno.jpg" alt></p>
<p>不过 Maybe 已经完全考虑到异常的存在，一切都在它的掌握之中。所有的异常都能够在编译时推导出来。当然要想推导出这些东西，你需要对整个编程模型做一定的封装会抽象，比如 DOM 就不能直接用了，而是需要一个中间层。</p>
<p>再来看下一个更普遍的例子 NPE：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="literal">null</span>.toString();</span><br></pre></td></tr></table></figure>

<p>elm 也不会发生。原因也很简单，因为 null 也会被包装起来，当你通过这个包装类型就行访问的时候，容器有能力避免这种情况，因此就可以不会发生异常。当然这里有一个很重要的前提就是<strong>可推导</strong>，而这正是函数式编程语言的特性。这部分内容超出了本文的讨论范围，不再这里说了。</p>
<h2 id="运行时异常可以恢复么？"><a href="#运行时异常可以恢复么？" class="headerlink" title="运行时异常可以恢复么？"></a>运行时异常可以恢复么？</h2><p>最后要讨论的一个主题是运行时异常是否可以恢复。先来解释一下，什么是运行时异常的恢复。 还是用上面的例子：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"start"</span>);</span><br><span class="line">  <span class="keyword">throw</span> <span class="number">1</span>;</span><br><span class="line">  <span class="built_in">console</span>.log(<span class="string">"end"</span>);</span><br><span class="line">&#125;</span><br><span class="line">t();</span><br></pre></td></tr></table></figure>

<p>这个我们已经知道了， <code>end</code> 是不会打印的。 尽管你这么写也是无济于事：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"start"</span>);</span><br><span class="line">    <span class="keyword">throw</span> <span class="number">1</span>;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"end"</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"relax, I can handle this"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">t();</span><br></pre></td></tr></table></figure>

<p>如果我想让它打印呢？我想让程序面对异常可以自己 recover 怎么办？我已经捕获这个错误， 并且我确信我可以处理，让流程继续走下去吧！如果有能力做到这个，这个就是<strong>运行时异常恢复</strong>。</p>
<p>遗憾地告诉你，据我所知，目前没有任何一个引擎能够做到这一点。</p>
<p>这个例子过于简单， 只能帮助我们理解什么是运行时异常恢复，但是不足以让我们看出这有什么用？</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gft490joaij30er0c4q3p.jpg" alt></p>
<p>我们来看一个更加复杂的例子，我们这里直接使用上面实现过的函数<code>divide</code>。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = divide(<span class="string">"foo"</span>, <span class="string">"bar"</span>);</span><br><span class="line">    alert(<span class="string">`you got <span class="subst">$&#123;res&#125;</span>`</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="number">1</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"被除数必须是除0之外的数"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">if</span> (err.code === <span class="number">2</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"除数必须是数字"</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="built_in">Error</span>(<span class="string">"不可预知的错误"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>如上代码，会进入 catch ，而不会 alert。因此对于用户来说， 应用程序是没有任何响应的。这是不可接受的。</p>
<blockquote>
<p>要吐槽一点的是这种事情真的是挺常见的，只不过大家用的不是 alert 罢了。</p>
</blockquote>
<p>如果我们的代码在进入 catch 之后还能够继续返回出错位置继续执行就好了。</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gftakhm2tqj31m20qcwmv.jpg" alt></p>
<p>如何实现异常中断的恢复呢？我刚刚说了：据我所知，目前没有任何一个引擎能够做到<strong>异常恢复</strong>。那么我就来<strong>发明一个新的语法</strong>解决这个问题。</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">t</span>(<span class="params"></span>) </span>&#123;</span><br><span class="line">  <span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> res = divide(<span class="string">"foo"</span>, <span class="string">"bar"</span>);</span><br><span class="line">    alert(<span class="string">`you got <span class="subst">$&#123;res&#125;</span>`</span>);</span><br><span class="line">  &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">    <span class="built_in">console</span>.log(<span class="string">"releax, I can handle this"</span>);</span><br><span class="line">    resume - <span class="number">1</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">t();</span><br></pre></td></tr></table></figure>

<p>上面的 resume 是我定义的一个关键字，功能是如果遇到异常，则返回到异常发生的地方，然后给当前发生异常的函数一个返回值 <strong>-1</strong>，并使得后续代码能够正常运行，不受影响。这其实是一种 fallback。</p>
<p>这绝对是一个超前的理念。当然挑战也非常大，对现有的体系冲击很大，很多东西都要改。我希望社区可以考虑把这个东西加到标准。</p>
<h2 id="最佳实践"><a href="#最佳实践" class="headerlink" title="最佳实践"></a>最佳实践</h2><p>通过前面的学习，你已经知道了异常是什么，异常是怎么产生的，以及如何正确处理异常（同步和异步）。接下来，我们谈一下异常处理的最佳实践。</p>
<p>我们平时开发一个应用。 如果站在生产者和消费者的角度来看的话。当我们使用别人封装的框架，库，模块，甚至是函数的时候，我们就是消费者。而当我们写的东西被他人使用的时候，我们就是生产者。</p>
<p>实际上，就算是生产者内部也会有多个模块构成，多个模块之间也会有生产者和消费者的再次身份转化。不过为了简单起见，本文不考虑这种关系。这里的生产者指的就是给他人使用的功能，是纯粹的生产者。</p>
<p>从这个角度出发，来看下异常处理的最佳实践。</p>
<h3 id="作为消费者"><a href="#作为消费者" class="headerlink" title="作为消费者"></a>作为消费者</h3><p>当作为消费者的时候，我们关心的是使用的功能是否会抛出异常，如果是，他们有哪些异常。比如：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> foo <span class="keyword">from</span> <span class="string">"lucifer"</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  foo.bar();</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">  <span class="comment">// 有哪些异常？</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>当然，理论上 foo.bar 可能产生任何异常，而不管它的 API 是这么写的。但是我们关心的是<strong>可预期的异常</strong>。因此你一定希望这个时候有一个 API 文档，详细列举了这个 API 可能产生的异常有哪些。</p>
<p>比如这个 foo.bar 4 种可能的异常 分别是 A，B，C 和 D。其中 A 和 B 是我可以处理的，而 C 和 D 是我不能处理的。那么我应该：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> foo <span class="keyword">from</span> <span class="string">"lucifer"</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  foo.bar();</span><br><span class="line">&#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">  <span class="keyword">if</span> (err.code === <span class="string">"A"</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"A happened"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (err.code === <span class="string">"B"</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="built_in">console</span>.log(<span class="string">"B happened"</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">throw</span> err;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>可以看出，不管是 C 和 D，还是 API 中没有列举的各种可能异常，我们的做法都是直接抛出。</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gft4ymgbxij30uv0hiwgo.jpg" alt></p>
<h3 id="作为生产者"><a href="#作为生产者" class="headerlink" title="作为生产者"></a>作为生产者</h3><p>如果你作为生产者，你要做的就是提供上面提到的详细的 API，告诉消费者你的可能错误有哪些。这样消费者就可以在 catch 中进行相应判断，处理异常情况。</p>
<p><img src="https://tva1.sinaimg.cn/large/007S8ZIlly1gft2iv4af5j30cz08zt9f.jpg" alt></p>
<p>你可以提供类似上图的错误表，让大家可以很快知道可能存在的<strong>可预知</strong>异常有哪些。不得不吐槽一句，在这一方面很多框架，库做的都很差。希望大家可以重视起来，努力维护良好的前端开发大环境。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文很长，如果你能耐心看完，你真得给可以给自己鼓个掌 👏👏👏。</p>
<p>我从什么是异常，以及异常的分类，让大家正确认识异常，简单来说异常就是一种数据结构而已。</p>
<p>接着，我又讲到了异常的传播和处理。这两个部分是紧密联系的。异常的传播和事件传播没有本质不同，主要不同是数据结构不同，思想是类似的。具体来说异常会从发生错误的调用处，沿着调用栈回退，直到第一个 catch 语句或者栈为空。如果栈为空都没有碰到一个 catch，则会抛出<strong>uncaught Error</strong>。 需要特别注意的是异步的异常处理，不过你如果对我讲的原理了解了，这都不是事。</p>
<p>然后，我提出了两个脑洞问题：</p>
<ul>
<li>彻底消除运行时异常可能么？</li>
<li>运行时异常可以恢复么？</li>
</ul>
<p>这两个问题非常值得研究，但由于篇幅原因，我这里只是给你讲个轮廓而已。如果你对这两个话题感兴趣，可以和我交流。</p>
<p>最后，我提到了前端异常处理的最佳实践。大家通过两种角色（生产者和消费者）的转换，认识一下不同决定关注点以及承担责任的不同。具体来说提到了 <strong>明确声明可能的异常</strong>以及 <strong>处理你应该处理的，不要吞没你不能处理的异常</strong>。当然这个最佳实践仍然是轮廓性的。如果大家想要一份 前端最佳实践 checklist，可以给我留言。留言人数较多的话，我考虑专门写一个前端最佳实践 checklist 类型的文章。</p>

      </div>
      
        <br>
        


  <section class='meta' id="footer-meta">
    <div class='new-meta-box'>
      
        
          <div class="new-meta-item date" itemprop="dateUpdated" datetime="2020-06-15T23:36:32+08:00">
  <a class='notlink'>
    <i class="fas fa-clock" aria-hidden="true"></i>
    <p>更新于 2020年6月15日</p>
  </a>
</div>

        
      
        
          
  
  <div class="new-meta-item meta-tags"><a class="tag" href="/blog/tags/前端/" rel="nofollow"><i class="fas fa-tag" aria-hidden="true"></i><p>前端</p></a></div> <div class="new-meta-item meta-tags"><a class="tag" href="/blog/tags/异常处理/" rel="nofollow"><i class="fas fa-tag" aria-hidden="true"></i><p>异常处理</p></a></div>


        
      
        
          
  <div class="new-meta-item share -mob-share-list">
  <div class="-mob-share-list share-body">
    
      
        <a class='qrcode' rel="external nofollow noopener noreferrer" href=''>
        
          <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/qrcode.png">
        
        </a>
      
    
      
        <a class="-mob-share-qq" title="QQ好友" rel="external nofollow noopener noreferrer"
          
          href="http://connect.qq.com/widget/shareqq/index.html?url=https://lucifer.ren/blog/2020/06/14/error-catch/&title=你不知道的前端异常处理（万字长文，建议收藏） | lucifer的网络博客&pics=https://avatars0.githubusercontent.com/u/12479470?s=400&u=442571e44cbd0b67e3503e9551d4445c78f593f8&v=4&summary=除了调试，处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道，就好像我们天天和 Bug 打交道一样。因此正确认识异常，并作出合适的异常处理就显得很重要了。
我们先尝试抛开前端这个限定条件，来看下更广泛意义上程序的报错以及异常处理。不管是什么语言，都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常，并针对其做相应的异常处理。
然而，很多人对异常的处理方式是事后修补，即某个异常发生的时候，增加对应的条件判断，这真的是一种非常低效的开发方式，非常不推荐大家这么做。那么究竟如何正确处理异常呢？由于不同语言有不同的特性，因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述，但是读者也可以稍加修改延伸到其他各个领域。

本文讨论的异常指的是软件异常，而非硬件异常。
"
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/qq.png">
          
        </a>
      
    
      
        <a class="-mob-share-qzone" title="QQ空间" rel="external nofollow noopener noreferrer"
          
          href="https://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=https://lucifer.ren/blog/2020/06/14/error-catch/&title=你不知道的前端异常处理（万字长文，建议收藏） | lucifer的网络博客&pics=https://avatars0.githubusercontent.com/u/12479470?s=400&u=442571e44cbd0b67e3503e9551d4445c78f593f8&v=4&summary=除了调试，处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道，就好像我们天天和 Bug 打交道一样。因此正确认识异常，并作出合适的异常处理就显得很重要了。
我们先尝试抛开前端这个限定条件，来看下更广泛意义上程序的报错以及异常处理。不管是什么语言，都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常，并针对其做相应的异常处理。
然而，很多人对异常的处理方式是事后修补，即某个异常发生的时候，增加对应的条件判断，这真的是一种非常低效的开发方式，非常不推荐大家这么做。那么究竟如何正确处理异常呢？由于不同语言有不同的特性，因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述，但是读者也可以稍加修改延伸到其他各个领域。

本文讨论的异常指的是软件异常，而非硬件异常。
"
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/qzone.png">
          
        </a>
      
    
      
        <a class="-mob-share-weibo" title="微博" rel="external nofollow noopener noreferrer"
          
          href="http://service.weibo.com/share/share.php?url=https://lucifer.ren/blog/2020/06/14/error-catch/&title=你不知道的前端异常处理（万字长文，建议收藏） | lucifer的网络博客&pics=https://avatars0.githubusercontent.com/u/12479470?s=400&u=442571e44cbd0b67e3503e9551d4445c78f593f8&v=4&summary=除了调试，处理异常或许是程序员编程时间占比最高的了。我们天天和各种异常打交道，就好像我们天天和 Bug 打交道一样。因此正确认识异常，并作出合适的异常处理就显得很重要了。
我们先尝试抛开前端这个限定条件，来看下更广泛意义上程序的报错以及异常处理。不管是什么语言，都会有异常的发生。而我们程序员要做的就是正确识别程序中的各种异常，并针对其做相应的异常处理。
然而，很多人对异常的处理方式是事后修补，即某个异常发生的时候，增加对应的条件判断，这真的是一种非常低效的开发方式，非常不推荐大家这么做。那么究竟如何正确处理异常呢？由于不同语言有不同的特性，因此异常处理方式也不尽相同。但是异常处理的思维框架一定是一致的。本文就前端异常进行详细阐述，但是读者也可以稍加修改延伸到其他各个领域。

本文讨论的异常指的是软件异常，而非硬件异常。
"
          
          >
          
            <img src="https://cdn.jsdelivr.net/gh/xaoxuu/assets@19.1.9/logo/128/weibo.png">
          
        </a>
      
    
  </div>
</div>



        
      
    </div>
  </section>


      
      
          <div class="prev-next">
              
                  <section class="prev">
                      <span class="art-item-left">
                          <h6><i class="fas fa-chevron-left" aria-hidden="true"></i>&nbsp;上一页</h6>
                          <h4>
                              <a href="/blog/2020/06/16/ts-generics/" rel="prev" title="你不知道的 TypeScript 泛型（万字长文，建议收藏）">
                                
                                    你不知道的 TypeScript 泛型（万字长文，建议收藏）
                                
                              </a>
                          </h4>
                          
                              
                              <h6 class="tags">
                                  <a class="tag" href="/blog/tags/前端/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> 前端</a> <a class="tag" href="/blog/tags/TypeScript/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> TypeScript</a> <a class="tag" href="/blog/tags/泛型/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> 泛型</a>
                              </h6>
                          
                      </span>
                  </section>
              
              
                  <section class="next">
                      <span class="art-item-right" aria-hidden="true">
                          <h6>下一页&nbsp;<i class="fas fa-chevron-right" aria-hidden="true"></i></h6>
                          <h4>
                              <a href="/blog/2020/06/13/删除问题/" rel="prev" title="一招吃遍力扣四道题，妈妈再也不用担心我被套路啦～">
                                  
                                      一招吃遍力扣四道题，妈妈再也不用担心我被套路啦～
                                  
                              </a>
                          </h4>
                          
                              
                              <h6 class="tags">
                                  <a class="tag" href="/blog/tags/LeetCode/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> LeetCode</a> <a class="tag" href="/blog/tags/经验分享/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> 经验分享</a> <a class="tag" href="/blog/tags/困难/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> 困难</a> <a class="tag" href="/blog/tags/中等/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> 中等</a> <a class="tag" href="/blog/tags/删除-k-个字符/"><i class="fas fa-tag fa-fw" aria-hidden="true"></i> 删除 k 个字符</a>
                              </h6>
                          
                      </span>
                  </section>
              
          </div>
      
    </section>
  </article>



  <!-- 显示推荐文章和评论 -->



  <article class="post white-box comments">
    <section class="article typo">
      <h4><i class="fas fa-comments fa-fw" aria-hidden="true"></i>&nbsp;评论</h4>
      
      
      
        <section id="comments">
          <div id="gitalk-container"></div>
        </section>
      
      
    </section>
  </article>






<!-- 根据页面mathjax变量决定是否加载MathJax数学公式js -->



  <script>
    window.subData = {
      title: '你不知道的前端异常处理（万字长文，建议收藏）',
      tools: true
    }
  </script>


</div>
<aside class='l_side'>
  
    
    
      
      
        
          
          
            
              <section class='widget author'>
  <div class='content pure'>
    
    
    
      <div class="social-wrapper">
        
          
            <a href="/blog/atom.xml"
              class="social fas fa-rss flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
          
            <a href="https://www.zhihu.com/people/lu-xiao-13-70/activities"
              class="social fab fa-zhihu flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
          
            <a href="mailto:azl397985856@gmail.com"
              class="social fas fa-envelope flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
          
            <a href="https://github.com/azl397985856"
              class="social fab fa-github flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
          
            <a href="https://music.163.com/playlist?id=978545815&userid=632167080"
              class="social fas fa-headphones-alt flat-btn"
              target="_blank"
              rel="external nofollow noopener noreferrer">
            </a>
          
        
      </div>
    
  </div>
</section>

            
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
            
              
  <section class='widget toc-wrapper'>
    
<header class='pure'>
  <div><i class="fas fa-list fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;本文目录</div>
  
    <!-- <div class='wrapper'><a class="s-toc rightBtn" rel="external nofollow noopener noreferrer" href="javascript:void(0)"><i class="fas fa-thumbtack fa-fw"></i></a></div> -->
  
</header>

    <div class='content pure'>
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#什么是异常"><span class="toc-text">什么是异常</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#异常的分类"><span class="toc-text">异常的分类</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#编译时异常"><span class="toc-text">编译时异常</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#运行时异常"><span class="toc-text">运行时异常</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#异常的传播"><span class="toc-text">异常的传播</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#一点小前提"><span class="toc-text">一点小前提</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#手动抛出-or-自动抛出"><span class="toc-text">手动抛出 or 自动抛出</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#异常-or-返回"><span class="toc-text">异常 or 返回</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#异常的处理"><span class="toc-text">异常的处理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#同步与异步"><span class="toc-text">同步与异步</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#彻底消除运行时异常可能么？"><span class="toc-text">彻底消除运行时异常可能么？</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#运行时异常可以恢复么？"><span class="toc-text">运行时异常可以恢复么？</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#最佳实践"><span class="toc-text">最佳实践</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#作为消费者"><span class="toc-text">作为消费者</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#作为生产者"><span class="toc-text">作为生产者</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#总结"><span class="toc-text">总结</span></a></li></ol>
    </div>
  </section>


            
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
        
          
          
            
              <section class='widget grid'>
  
<header class='pure'>
  <div><i class="fas fa-map-signs fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;我的开源项目</div>
  
</header>

  <div class='content pure'>
    <ul class="grid navgation">
      
        <li><a class="flat-box" title="https://github.com/azl397985856/leetcode" href="https://github.com/azl397985856/leetcode"
          
          
          id="https:github.comazl397985856leetcode">
          
            <i class="fab fa-github fa-fw" aria-hidden="true"></i>
          
          LeetCode
        </a></li>
      
        <li><a class="flat-box" title="https://github.com/azl397985856/fe-interview" href="https://github.com/azl397985856/fe-interview"
          
          
          id="https:github.comazl397985856fe-interview">
          
            <i class="fab fa-github fa-fw" aria-hidden="true"></i>
          
          大前端
        </a></li>
      
        <li><a class="flat-box" title="https://github.com/azl397985856/daily-featured" href="https://github.com/azl397985856/daily-featured"
          
          
          id="https:github.comazl397985856daily-featured">
          
            <i class="fab fa-github fa-fw" aria-hidden="true"></i>
          
          每日一荐
        </a></li>
      
    </ul>
  </div>
</section>

            
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
        
          
          
        
          
          
            
              
  <section class='widget category'>
    
<header class='pure'>
  <div><i class="fas fa-folder-open fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;全部分类</div>
  
    <a class="rightBtn"
    
      rel="external nofollow noopener noreferrer"
    
    
    href="/blog/categories/"
    title="categories/">
    <i class="fas fa-expand-arrows-alt fa-fw"></i></a>
  
</header>

    <div class='content pure'>
      <ul class="entry">
        
          <li><a class="flat-box" title="/blog/categories/91天学算法/" href="/blog/categories/91天学算法/"><div class='name'>91天学算法</div><div class='badge'>(4)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/Easy/" href="/blog/categories/Easy/"><div class='name'>Easy</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/Hard/" href="/blog/categories/Hard/"><div class='name'>Hard</div><div class='badge'>(3)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/LeetCode/" href="/blog/categories/LeetCode/"><div class='name'>LeetCode</div><div class='badge'>(15)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/LeetCode/LeetCode题解书/" href="/blog/categories/LeetCode/LeetCode题解书/"><div class='name'>LeetCode题解书</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/LeetCode/动态规划/" href="/blog/categories/LeetCode/动态规划/"><div class='name'>动态规划</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/Medium/" href="/blog/categories/Medium/"><div class='name'>Medium</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/React/" href="/blog/categories/React/"><div class='name'>React</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/TypeScript/" href="/blog/categories/TypeScript/"><div class='name'>TypeScript</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/中等/" href="/blog/categories/中等/"><div class='name'>中等</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/书/" href="/blog/categories/书/"><div class='name'>书</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/书/算法/" href="/blog/categories/书/算法/"><div class='name'>算法</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/书摘/" href="/blog/categories/书摘/"><div class='name'>书摘</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/二叉树/" href="/blog/categories/二叉树/"><div class='name'>二叉树</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/前端/" href="/blog/categories/前端/"><div class='name'>前端</div><div class='badge'>(14)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/TypeScript/" href="/blog/categories/前端/TypeScript/"><div class='name'>TypeScript</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/TypeScript/泛型/" href="/blog/categories/前端/TypeScript/泛型/"><div class='name'>泛型</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/eslint/" href="/blog/categories/前端/eslint/"><div class='name'>eslint</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/web-component/" href="/blog/categories/前端/web-component/"><div class='name'>web-component</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/测试/" href="/blog/categories/前端/测试/"><div class='name'>测试</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/浏览器/" href="/blog/categories/前端/浏览器/"><div class='name'>浏览器</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/算法/" href="/blog/categories/前端/算法/"><div class='name'>算法</div><div class='badge'>(4)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/前端/组件化/" href="/blog/categories/前端/组件化/"><div class='name'>组件化</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/力扣加加/" href="/blog/categories/力扣加加/"><div class='name'>力扣加加</div><div class='badge'>(5)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/学习方法/" href="/blog/categories/学习方法/"><div class='name'>学习方法</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/异议！/" href="/blog/categories/异议！/"><div class='name'>异议！</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/技术大会/" href="/blog/categories/技术大会/"><div class='name'>技术大会</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/技术大会/D2/" href="/blog/categories/技术大会/D2/"><div class='name'>D2</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/技术大会/Google-IO/" href="/blog/categories/技术大会/Google-IO/"><div class='name'>Google IO</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/技术大会/JSConf/" href="/blog/categories/技术大会/JSConf/"><div class='name'>JSConf</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/技术大会/QCon/" href="/blog/categories/技术大会/QCon/"><div class='name'>QCon</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/技术大会/React-Conf/" href="/blog/categories/技术大会/React-Conf/"><div class='name'>React Conf</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/数据结构/" href="/blog/categories/数据结构/"><div class='name'>数据结构</div><div class='badge'>(23)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/hashtable/" href="/blog/categories/数据结构/hashtable/"><div class='name'>hashtable</div><div class='badge'>(6)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/二叉搜索树/" href="/blog/categories/数据结构/二叉搜索树/"><div class='name'>二叉搜索树</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/图/" href="/blog/categories/数据结构/图/"><div class='name'>图</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/字符串/" href="/blog/categories/数据结构/字符串/"><div class='name'>字符串</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/平衡二叉树/" href="/blog/categories/数据结构/平衡二叉树/"><div class='name'>平衡二叉树</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/数组/" href="/blog/categories/数据结构/数组/"><div class='name'>数组</div><div class='badge'>(6)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/算法/" href="/blog/categories/数据结构/算法/"><div class='name'>算法</div><div class='badge'>(5)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/数据结构/链表/" href="/blog/categories/数据结构/链表/"><div class='name'>链表</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/数据结构，二叉树/" href="/blog/categories/数据结构，二叉树/"><div class='name'>数据结构，二叉树</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/数据结构，单调栈/" href="/blog/categories/数据结构，单调栈/"><div class='name'>数据结构，单调栈</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/数据结构，字符串/" href="/blog/categories/数据结构，字符串/"><div class='name'>数据结构，字符串</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/数据结构，数组/" href="/blog/categories/数据结构，数组/"><div class='name'>数据结构，数组</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/日记/" href="/blog/categories/日记/"><div class='name'>日记</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/日记/技术/" href="/blog/categories/日记/技术/"><div class='name'>技术</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/每日一荐/" href="/blog/categories/每日一荐/"><div class='name'>每日一荐</div><div class='badge'>(6)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/每日一荐/2019-09/" href="/blog/categories/每日一荐/2019-09/"><div class='name'>2019-09</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/每日一荐/2019-10/" href="/blog/categories/每日一荐/2019-10/"><div class='name'>2019-10</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/每日一荐/2019-11/" href="/blog/categories/每日一荐/2019-11/"><div class='name'>2019-11</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/每日一荐/2019-12/" href="/blog/categories/每日一荐/2019-12/"><div class='name'>2019-12</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/每日一荐/2020-01/" href="/blog/categories/每日一荐/2020-01/"><div class='name'>2020-01</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/每日一荐/2020-03/" href="/blog/categories/每日一荐/2020-03/"><div class='name'>2020-03</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/浏览器/" href="/blog/categories/浏览器/"><div class='name'>浏览器</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/浏览器/事件/" href="/blog/categories/浏览器/事件/"><div class='name'>事件</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/电影/" href="/blog/categories/电影/"><div class='name'>电影</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/电影/观后感/" href="/blog/categories/电影/观后感/"><div class='name'>观后感</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/算法/" href="/blog/categories/算法/"><div class='name'>算法</div><div class='badge'>(20)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/BFS/" href="/blog/categories/算法/BFS/"><div class='name'>BFS</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/DFS/" href="/blog/categories/算法/DFS/"><div class='name'>DFS</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/二分法/" href="/blog/categories/算法/二分法/"><div class='name'>二分法</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/位运算/" href="/blog/categories/算法/位运算/"><div class='name'>位运算</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/前缀和/" href="/blog/categories/算法/前缀和/"><div class='name'>前缀和</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/动态规划/" href="/blog/categories/算法/动态规划/"><div class='name'>动态规划</div><div class='badge'>(4)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/双指针/" href="/blog/categories/算法/双指针/"><div class='name'>双指针</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/回文/" href="/blog/categories/算法/回文/"><div class='name'>回文</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/回溯/" href="/blog/categories/算法/回溯/"><div class='name'>回溯</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/子序列/" href="/blog/categories/算法/子序列/"><div class='name'>子序列</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/就地算法/" href="/blog/categories/算法/就地算法/"><div class='name'>就地算法</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/布隆过滤器/" href="/blog/categories/算法/布隆过滤器/"><div class='name'>布隆过滤器</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/循环移位/" href="/blog/categories/算法/循环移位/"><div class='name'>循环移位</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/数学/" href="/blog/categories/算法/数学/"><div class='name'>数学</div><div class='badge'>(2)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/概率/" href="/blog/categories/算法/概率/"><div class='name'>概率</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/母题/" href="/blog/categories/算法/母题/"><div class='name'>母题</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/状态压缩/" href="/blog/categories/算法/状态压缩/"><div class='name'>状态压缩</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/背包问题/" href="/blog/categories/算法/背包问题/"><div class='name'>背包问题</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/递归/" href="/blog/categories/算法/递归/"><div class='name'>递归</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box child" title="/blog/categories/算法/链表反转/" href="/blog/categories/算法/链表反转/"><div class='name'>链表反转</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/算法，动态规划/" href="/blog/categories/算法，动态规划/"><div class='name'>算法，动态规划</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/算法，序列化/" href="/blog/categories/算法，序列化/"><div class='name'>算法，序列化</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/算法，滑动窗口/" href="/blog/categories/算法，滑动窗口/"><div class='name'>算法，滑动窗口</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/经验分享/" href="/blog/categories/经验分享/"><div class='name'>经验分享</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/编程之美/" href="/blog/categories/编程之美/"><div class='name'>编程之美</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/解题模板/" href="/blog/categories/解题模板/"><div class='name'>解题模板</div><div class='badge'>(1)</div></a></li>
        
          <li><a class="flat-box" title="/blog/categories/贪婪/" href="/blog/categories/贪婪/"><div class='name'>贪婪</div><div class='badge'>(1)</div></a></li>
        
      </ul>
    </div>
  </section>


            
          
        
          
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
            
              
  <section class='widget tagcloud'>
    
<header class='pure'>
  <div><i class="fas fa-tags fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;热门标签</div>
  
    <a class="rightBtn"
    
      rel="external nofollow noopener noreferrer"
    
    
    href="/blog/tags/"
    title="tags/">
    <i class="fas fa-expand-arrows-alt fa-fw"></i></a>
  
</header>

    <div class='content pure'>
      <a href="/blog/tags/91天学算法/" style="font-size: 17px; color: #858585">91天学算法</a> <a href="/blog/tags/BFS/" style="font-size: 14px; color: #999">BFS</a> <a href="/blog/tags/BigPipe/" style="font-size: 14px; color: #999">BigPipe</a> <a href="/blog/tags/Canvas/" style="font-size: 14px; color: #999">Canvas</a> <a href="/blog/tags/Chrome/" style="font-size: 14px; color: #999">Chrome</a> <a href="/blog/tags/D2/" style="font-size: 14px; color: #999">D2</a> <a href="/blog/tags/Easy/" style="font-size: 14px; color: #999">Easy</a> <a href="/blog/tags/Floyd-Warshall/" style="font-size: 14px; color: #999">Floyd-Warshall</a> <a href="/blog/tags/Google-IO/" style="font-size: 14px; color: #999">Google IO</a> <a href="/blog/tags/Hard/" style="font-size: 14px; color: #999">Hard</a> <a href="/blog/tags/JSConf/" style="font-size: 14px; color: #999">JSConf</a> <a href="/blog/tags/LeetCode/" style="font-size: 22px; color: #636363">LeetCode</a> <a href="/blog/tags/LeetCode日记/" style="font-size: 20px; color: #707070">LeetCode日记</a> <a href="/blog/tags/Mac/" style="font-size: 14px; color: #999">Mac</a> <a href="/blog/tags/Medium/" style="font-size: 15px; color: #929292">Medium</a> <a href="/blog/tags/PPT/" style="font-size: 14px; color: #999">PPT</a> <a href="/blog/tags/QCon/" style="font-size: 14px; color: #999">QCon</a> <a href="/blog/tags/RFC/" style="font-size: 14px; color: #999">RFC</a> <a href="/blog/tags/React/" style="font-size: 14px; color: #999">React</a> <a href="/blog/tags/TypeScript/" style="font-size: 16px; color: #8b8b8b">TypeScript</a> <a href="/blog/tags/eslint/" style="font-size: 14px; color: #999">eslint</a> <a href="/blog/tags/immutable/" style="font-size: 14px; color: #999">immutable</a> <a href="/blog/tags/immutablejs/" style="font-size: 14px; color: #999">immutablejs</a> <a href="/blog/tags/vue/" style="font-size: 14px; color: #999">vue</a> <a href="/blog/tags/web-component/" style="font-size: 14px; color: #999">web-component</a> <a href="/blog/tags/中等/" style="font-size: 14px; color: #999">中等</a> <a href="/blog/tags/书/" style="font-size: 14px; color: #999">书</a> <a href="/blog/tags/书摘/" style="font-size: 14px; color: #999">书摘</a> <a href="/blog/tags/事件/" style="font-size: 14px; color: #999">事件</a> <a href="/blog/tags/事件循环/" style="font-size: 14px; color: #999">事件循环</a> <a href="/blog/tags/二叉树/" style="font-size: 16px; color: #8b8b8b">二叉树</a> <a href="/blog/tags/位运算/" style="font-size: 14px; color: #999">位运算</a> <a href="/blog/tags/删除-k-个字符/" style="font-size: 14px; color: #999">删除 k 个字符</a> <a href="/blog/tags/前端/" style="font-size: 21px; color: #696969">前端</a> <a href="/blog/tags/前缀和/" style="font-size: 15px; color: #929292">前缀和</a> <a href="/blog/tags/前缀表达式/" style="font-size: 14px; color: #999">前缀表达式</a> <a href="/blog/tags/力扣加加/" style="font-size: 18px; color: #7e7e7e">力扣加加</a> <a href="/blog/tags/动态规划/" style="font-size: 18px; color: #7e7e7e">动态规划</a> <a href="/blog/tags/单元测试/" style="font-size: 14px; color: #999">单元测试</a> <a href="/blog/tags/困难/" style="font-size: 14px; color: #999">困难</a> <a href="/blog/tags/图/" style="font-size: 14px; color: #999">图</a> <a href="/blog/tags/图片处理/" style="font-size: 14px; color: #999">图片处理</a> <a href="/blog/tags/字符串/" style="font-size: 14px; color: #999">字符串</a> <a href="/blog/tags/字节跳动/" style="font-size: 14px; color: #999">字节跳动</a> <a href="/blog/tags/学习方法/" style="font-size: 15px; color: #929292">学习方法</a> <a href="/blog/tags/序列化/" style="font-size: 14px; color: #999">序列化</a> <a href="/blog/tags/异常处理/" style="font-size: 14px; color: #999">异常处理</a> <a href="/blog/tags/异议！/" style="font-size: 14px; color: #999">异议！</a> <a href="/blog/tags/循环移位/" style="font-size: 14px; color: #999">循环移位</a> <a href="/blog/tags/微前端/" style="font-size: 14px; color: #999">微前端</a> <a href="/blog/tags/必备软件/" style="font-size: 14px; color: #999">必备软件</a> <a href="/blog/tags/我的书/" style="font-size: 14px; color: #999">我的书</a> <a href="/blog/tags/扩展程序/" style="font-size: 14px; color: #999">扩展程序</a> <a href="/blog/tags/技术大会/" style="font-size: 14px; color: #999">技术大会</a> <a href="/blog/tags/技术调研/" style="font-size: 14px; color: #999">技术调研</a> <a href="/blog/tags/技能/" style="font-size: 14px; color: #999">技能</a> <a href="/blog/tags/数学/" style="font-size: 15px; color: #929292">数学</a> <a href="/blog/tags/数据结构/" style="font-size: 23px; color: #5c5c5c">数据结构</a> <a href="/blog/tags/数据结构，算法，LeetCode-日记，Hard/" style="font-size: 15px; color: #929292">数据结构，算法，LeetCode 日记，Hard</a> <a href="/blog/tags/数据结构，算法，LeetCode-日记，中等/" style="font-size: 14px; color: #999">数据结构，算法，LeetCode 日记，中等</a> <a href="/blog/tags/数组/" style="font-size: 15px; color: #929292">数组</a> <a href="/blog/tags/日记/" style="font-size: 15px; color: #929292">日记</a> <a href="/blog/tags/最长上升子序列/" style="font-size: 14px; color: #999">最长上升子序列</a> <a href="/blog/tags/最长公共子序列/" style="font-size: 14px; color: #999">最长公共子序列</a> <a href="/blog/tags/概率/" style="font-size: 14px; color: #999">概率</a> <a href="/blog/tags/母题/" style="font-size: 14px; color: #999">母题</a> <a href="/blog/tags/每日一荐/" style="font-size: 19px; color: #777">每日一荐</a> <a href="/blog/tags/泛型/" style="font-size: 15px; color: #929292">泛型</a> <a href="/blog/tags/测试/" style="font-size: 14px; color: #999">测试</a> <a href="/blog/tags/浏览器/" style="font-size: 15px; color: #929292">浏览器</a> <a href="/blog/tags/滑动窗口/" style="font-size: 15px; color: #929292">滑动窗口</a> <a href="/blog/tags/滤镜/" style="font-size: 14px; color: #999">滤镜</a> <a href="/blog/tags/状态压缩/" style="font-size: 14px; color: #999">状态压缩</a> <a href="/blog/tags/状态机/" style="font-size: 14px; color: #999">状态机</a> <a href="/blog/tags/电影/" style="font-size: 15px; color: #929292">电影</a> <a href="/blog/tags/监控/" style="font-size: 14px; color: #999">监控</a> <a href="/blog/tags/算法/" style="font-size: 24px; color: #555">算法</a> <a href="/blog/tags/算法提高班/" style="font-size: 18px; color: #7e7e7e">算法提高班</a> <a href="/blog/tags/算法系列/" style="font-size: 17px; color: #858585">算法系列</a> <a href="/blog/tags/组件化/" style="font-size: 14px; color: #999">组件化</a> <a href="/blog/tags/经验分享/" style="font-size: 15px; color: #929292">经验分享</a> <a href="/blog/tags/编程之美/" style="font-size: 14px; color: #999">编程之美</a> <a href="/blog/tags/草稿/" style="font-size: 14px; color: #999">草稿</a> <a href="/blog/tags/装机/" style="font-size: 14px; color: #999">装机</a> <a href="/blog/tags/解题模板/" style="font-size: 14px; color: #999">解题模板</a> <a href="/blog/tags/贪婪/" style="font-size: 14px; color: #999">贪婪</a> <a href="/blog/tags/贪心/" style="font-size: 14px; color: #999">贪心</a> <a href="/blog/tags/递归/" style="font-size: 14px; color: #999">递归</a> <a href="/blog/tags/链表/" style="font-size: 15px; color: #929292">链表</a> <a href="/blog/tags/陷阱题/" style="font-size: 14px; color: #999">陷阱题</a>
    </div>
  </section>


            
          
        
          
          
        
          
          
        
      
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
            
              <section class='widget list'>
  
<header class='pure'>
  <div><i class="fas fa-thumbs-up fa-fw" aria-hidden="true"></i>&nbsp;&nbsp;强烈推荐</div>
  
</header>

  <div class='content pure'>
    <ul class="entry">
      
        <li><a class="flat-box" title="https://xaoxuu.com/wiki/hexo.sh/" href="https://xaoxuu.com/wiki/hexo.sh/"
          
          
          >
          <div class='name'>
            
              <i class=" fa-fw" aria-hidden="true"></i>
            
            &nbsp;&nbsp;Hexo脚本（Mac）
          </div>
          
        </a></li>
      
        <li><a class="flat-box" title="https://xaoxuu.com/wiki/vim-cn.sh/" href="https://xaoxuu.com/wiki/vim-cn.sh/"
          
          
          >
          <div class='name'>
            
              <i class=" fa-fw" aria-hidden="true"></i>
            
            &nbsp;&nbsp;图床脚本（Mac）
          </div>
          
        </a></li>
      
        <li><a class="flat-box" title="https://yasuotu.com" href="https://yasuotu.com"
          
          
          >
          <div class='name'>
            
              <i class=" fa-fw" aria-hidden="true"></i>
            
            &nbsp;&nbsp;图片在线压缩
          </div>
          
        </a></li>
      
        <li><a class="flat-box" title="https://realfavicongenerator.net" href="https://realfavicongenerator.net"
          
          
          >
          <div class='name'>
            
              <i class=" fa-fw" aria-hidden="true"></i>
            
            &nbsp;&nbsp;生成Favicon
          </div>
          
        </a></li>
      
        <li><a class="flat-box" title="https://mxclub.github.io/resume/" href="https://mxclub.github.io/resume/"
          
          
          >
          <div class='name'>
            
              <i class=" fa-fw" aria-hidden="true"></i>
            
            &nbsp;&nbsp;简历主题
          </div>
          
        </a></li>
      
    </ul>
  </div>
</section>

            
          
        
      
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
          
          
        
      
    

  
</aside>

<footer id="footer" class="clearfix">
   
  <div class="social-wrapper">
     
    <a
      href="/blog/atom.xml"
      class="social fas fa-rss flat-btn"
      target="_blank"
      rel="external nofollow noopener noreferrer"
    >
    </a>
      
    <a
      href="https://www.zhihu.com/people/lu-xiao-13-70/activities"
      class="social fab fa-zhihu flat-btn"
      target="_blank"
      rel="external nofollow noopener noreferrer"
    >
    </a>
      
    <a
      href="mailto:azl397985856@gmail.com"
      class="social fas fa-envelope flat-btn"
      target="_blank"
      rel="external nofollow noopener noreferrer"
    >
    </a>
      
    <a
      href="https://github.com/azl397985856"
      class="social fab fa-github flat-btn"
      target="_blank"
      rel="external nofollow noopener noreferrer"
    >
    </a>
      
    <a
      href="https://music.163.com/playlist?id=978545815&amp;userid=632167080"
      class="social fas fa-headphones-alt flat-btn"
      target="_blank"
      rel="external nofollow noopener noreferrer"
    >
    </a>
     
  </div>
  
  <br />
  <div><p>博客内容遵循 <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh">署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议</a></p>
</div>
  <div>
    本站使用
    <a href="https://xaoxuu.com/wiki/material-x/" target="_blank" class="codename"
      >Material X</a
    >
    作为主题  ， 总访问量为
    <span id="busuanzi_value_site_pv"
      ><i class="fas fa-spinner fa-spin fa-fw" aria-hidden="true"></i
    ></span>
    次  。
  </div>

  <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span>
  <script>
    var now = new Date();
    function createtime() {
      var grt = new Date("08/10/2018 17:38:00"); //在此处修改你的建站时间，格式：月/日/年 时:分:秒
      now.setTime(now.getTime() + 250);
      days = (now - grt) / 1000 / 60 / 60 / 24;
      dnum = Math.floor(days);
      hours = (now - grt) / 1000 / 60 / 60 - 24 * dnum;
      hnum = Math.floor(hours);
      if (String(hnum).length == 1) {
        hnum = "0" + hnum;
      }
      minutes = (now - grt) / 1000 / 60 - 24 * 60 * dnum - 60 * hnum;
      mnum = Math.floor(minutes);
      if (String(mnum).length == 1) {
        mnum = "0" + mnum;
      }
      seconds =
        (now - grt) / 1000 - 24 * 60 * 60 * dnum - 60 * 60 * hnum - 60 * mnum;
      snum = Math.round(seconds);
      if (String(snum).length == 1) {
        snum = "0" + snum;
      }
      document.getElementById("timeDate").innerHTML =
        "本站已安全运行 " + dnum + " 天 ";
      document.getElementById("times").innerHTML =
        hnum + " 小时 " + mnum + " 分 " + snum + " 秒";
    }
    setInterval("createtime()", 250);
  </script>
</footer>
<script>
  setLoadingBarProgress(80);
</script>


      <script>setLoadingBarProgress(60);</script>
    </div>
    <a class="s-top fas fa-arrow-up fa-fw" href='javascript:void(0)'></a>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>

  <script>
    var GOOGLE_CUSTOM_SEARCH_API_KEY = "";
    var GOOGLE_CUSTOM_SEARCH_ENGINE_ID = "";
    var ALGOLIA_API_KEY = "";
    var ALGOLIA_APP_ID = "";
    var ALGOLIA_INDEX_NAME = "";
    var AZURE_SERVICE_NAME = "";
    var AZURE_INDEX_NAME = "";
    var AZURE_QUERY_KEY = "";
    var BAIDU_API_ID = "";
    var SEARCH_SERVICE = "hexo" || "hexo";
    var ROOT = "/blog/"||"/";
    if(!ROOT.endsWith('/'))ROOT += '/';
  </script>

<script src="//instant.page/1.2.2" type="module" integrity="sha384-2xV8M5griQmzyiY3CDqh1dn4z3llDVqZDqzjzcY+jCBCk/a5fXJmuZ/40JJAPeoU"></script>


  <script async src="https://cdn.jsdelivr.net/npm/scrollreveal@4.0.5/dist/scrollreveal.min.js"></script>
  <script type="text/javascript">
    $(function() {
      const $reveal = $('.reveal');
      if ($reveal.length === 0) return;
      const sr = ScrollReveal({ distance: 0 });
      sr.reveal('.reveal');
    });
  </script>


  <script src="https://cdn.jsdelivr.net/npm/node-waves@0.7.6/dist/waves.min.js"></script>
  <script type="text/javascript">
    $(function() {
      Waves.attach('.flat-btn', ['waves-button']);
      Waves.attach('.float-btn', ['waves-button', 'waves-float']);
      Waves.attach('.float-btn-light', ['waves-button', 'waves-float', 'waves-light']);
      Waves.attach('.flat-box', ['waves-block']);
      Waves.attach('.float-box', ['waves-block', 'waves-float']);
      Waves.attach('.waves-image');
      Waves.init();
    });
  </script>


  <script async src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-busuanzi@2.3/js/busuanzi.pure.mini.js"></script>




  
  
  
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-backstretch/2.0.4/jquery.backstretch.min.js"></script>
    <script type="text/javascript">
      $(function(){
        if ('.cover') {
          $('.cover').backstretch(
          ["https://img.vim-cn.com/29/91197b04c13f512f734a76d4ac422d89dbe229.jpg"],
          {
            duration: "6000",
            fade: "2500"
          });
        } else {
          $.backstretch(
          ["https://img.vim-cn.com/29/91197b04c13f512f734a76d4ac422d89dbe229.jpg"],
          {
            duration: "6000",
            fade: "2500"
          });
        }
      });
    </script>
  







  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.css">
  <script src="https://cdn.jsdelivr.net/npm/gitalk@1/dist/gitalk.min.js"></script>
  <script type="text/javascript">
    var gitalk = new Gitalk({
      clientID: "aea1377036afe4cd0343",
      clientSecret: "815c638dea8644b7a4b97905707cf72b45555f6d",
      repo: "blog",
      owner: "azl397985856",
      admin: "azl397985856",
      
        id: location.pathname,      // Ensure uniqueness and length less than 50
      
      distractionFreeMode: false  // Facebook-like distraction free mode
    });
    gitalk.render('gitalk-container');
  </script>





  <script src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-material-x@19.9/js/app.js"></script>


  <script src="https://cdn.jsdelivr.net/gh/xaoxuu/cdn-material-x@19.9/js/search.js"></script>




<!-- 复制 -->
<script src="https://cdn.jsdelivr.net/npm/clipboard@2/dist/clipboard.min.js"></script>
<script>
  let COPY_SUCCESS = "复制成功";
  let COPY_FAILURE = "复制失败";
  /*页面载入完成后，创建复制按钮*/
  !function (e, t, a) {
    /* code */
    var initCopyCode = function(){
      var copyHtml = '';
      copyHtml += '<button class="btn-copy" data-clipboard-snippet="">';
      copyHtml += '  <i class="fa fa-copy"></i><span>复制</span>';
      copyHtml += '</button>';
      $(".highlight .code pre").before(copyHtml);
      var clipboard = new ClipboardJS('.btn-copy', {
        target: function(trigger) {
          return trigger.nextElementSibling;
        }
      });

      clipboard.on('success', function(e) {
        //您可以加入成功提示
        console.info('Action:', e.action);
        console.info('Text:', e.text);
        console.info('Trigger:', e.trigger);
        success_prompt(COPY_SUCCESS);
        e.clearSelection();
      });
      clipboard.on('error', function(e) {
        //您可以加入失败提示
        console.error('Action:', e.action);
        console.error('Trigger:', e.trigger);
        fail_prompt(COPY_FAILURE);
      });
    }
    initCopyCode();

  }(window, document);

  /**
   * 弹出式提示框，默认1.5秒自动消失
   * @param message 提示信息
   * @param style 提示样式，有alert-success、alert-danger、alert-warning、alert-info
   * @param time 消失时间
   */
  var prompt = function (message, style, time)
  {
      style = (style === undefined) ? 'alert-success' : style;
      time = (time === undefined) ? 1500 : time*1000;
      $('<div>')
          .appendTo('body')
          .addClass('alert ' + style)
          .html(message)
          .show()
          .delay(time)
          .fadeOut();
  };

  // 成功提示
  var success_prompt = function(message, time)
  {
      prompt(message, 'alert-success', time);
  };

  // 失败提示
  var fail_prompt = function(message, time)
  {
      prompt(message, 'alert-danger', time);
  };

  // 提醒
  var warning_prompt = function(message, time)
  {
      prompt(message, 'alert-warning', time);
  };

  // 信息提示
  var info_prompt = function(message, time)
  {
      prompt(message, 'alert-info', time);
  };

</script>


<!-- fancybox -->
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>
<script>
  let LAZY_LOAD_IMAGE = "";
  $(".article-entry").find("fancybox").find("img").each(function () {
      var element = document.createElement("a");
      $(element).attr("data-fancybox", "gallery");
      $(element).attr("href", $(this).attr("src"));
      /* 图片采用懒加载处理时,
       * 一般图片标签内会有个属性名来存放图片的真实地址，比如 data-original,
       * 那么此处将原本的属性名src替换为对应属性名data-original,
       * 修改如下
       */
       if (LAZY_LOAD_IMAGE) {
         $(element).attr("href", $(this).attr("data-original"));
       }
      $(this).wrap(element);
  });
</script>





  <script>setLoadingBarProgress(100);</script>
  <!-- <script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
  <script>
      const btw = new BTWPlugin();
      btw.init({
          id: 'container',
          blogId: '17446-1571644985832-648',
          name: '脑洞前端',
          qrcode: 'https://lucifer-1259702774.cos.ap-shanghai.myqcloud.com/2019-09-19-085421.jpg',
          keyword: 'more',
      });
  </script> -->
</body>
</html>
